package jp.co.systemi.asp.workflow.model;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import jp.co.systemi.asp.utils.CalcDate;
import jp.co.systemi.asp.workflow.Main;
import jp.co.systemi.asp.workflow.data.Apply;
import jp.co.systemi.asp.workflow.data.Approver;
import jp.co.systemi.asp.workflow.data.ApproverMeta;
import jp.co.systemi.asp.workflow.data.Common;
import jp.co.systemi.asp.workflow.data.PaidHoliday;
import jp.co.systemi.asp.workflow.data.PaidHolidayMeta;
import jp.co.systemi.asp.workflow.data.Route;
import jp.co.systemi.asp.workflow.data.RouteMeta;
import jp.co.systemi.asp.workflow.data.User;
import jp.co.systemi.asp.workflow.data.UserGroup;
import jp.co.systemi.asp.workflow.data.UserMeta;
import jp.co.systemi.asp.workflow.mail.Mail;
import jp.co.systemi.asp.workflow.peer.PaidHolidayPeer;
import jp.co.systemi.asp.workflow.peer.UserGroupPeer;
import jp.co.systemi.asp.workflow.peer.UserPeer;

import org.datanucleus.util.StringUtils;
import org.slim3.datastore.Datastore;
import org.slim3.datastore.GlobalTransaction;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

public class PaidHolidayApplyModel implements Serializable {
    private static final long serialVersionUID = -8020284536534716248L;
    private static final Logger log = Logger
            .getLogger(PaidHolidayApplyModel.class.getName());
    private static ResourceBundle resources = ResourceBundle
            .getBundle(PaidHolidayApplyModel.class.getName().replace(".", "/"));
    public static final String CATEGORY = "有給休暇";
    private String applyee = "";
    private User mailSender = null;
    User applyeeUser = null;
    PaidHoliday master = null;
    List<PaidHoliday> toApply = new ArrayList<PaidHoliday>(); // 申請分
    List<PaidHoliday> holidays = new ArrayList<PaidHoliday>(); // 承認済み、まだ消化してない分
    List<Approver> approvers = new ArrayList<Approver>();

    public PaidHolidayApplyModel(String applyee, User mailSender) {
        this.applyee = applyee;
        this.mailSender = mailSender;
        UserMeta um = UserMeta.get();

        applyeeUser = Datastore.query(um).filter(um.loginId.equal(applyee))
                .asSingle();
    }

    public void regEditingHoliday(PaidHoliday edit) throws Exception {

        // 入力チェック：ボタンによって、チェック内容が異なるため、addValidatorを使わない
        if (edit.getStartDate() == null) {
            throw new Exception(resources.getString("startDate.empty"));
        }
        if (edit.getStartDate().compareTo(Calendar.getInstance().getTime()) <= 0) {
            throw new Exception(resources.getString("startDate.from.tomorrow"));
        }
        if (edit.getEndDate() == null) {
            throw new Exception(resources.getString("endDate.empty"));
        }
        if (edit.getEndDate().compareTo(edit.getStartDate()) < 0) {
            throw new Exception(resources.getString("endDate.after.startDate"));
        }
        // 月を跨らないこと
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
        if (!sdf.format(edit.getStartDate()).equals(
                sdf.format(edit.getEndDate()))) {
            throw new Exception(resources.getString("multi.months.error"));
        }
        if (edit.getDays() == null || edit.getDays() < 0.5) {
            throw new Exception(resources.getString("days.empty"));
        }
        Calendar d = Calendar.getInstance();
        d.setTime(edit.getStartDate());
        long startDays = new CalcDate(d.get(Calendar.YEAR), d
                .get(Calendar.MONTH), d.get(Calendar.DATE)).getUnixDay();
        d.setTime(edit.getEndDate());
        long endDays = new CalcDate(d.get(Calendar.YEAR),
                d.get(Calendar.MONTH), d.get(Calendar.DATE)).getUnixDay();
        // long diff = endDays - startDays + 1;

        // 　元々休日ではないこと
        int workday = 0;
        for (long day = startDays; day <= endDays; day++) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(edit.getStartDate());
            cal.add(Calendar.DATE, (int) (day - startDays));
            if (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY
                    || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                // TODO: 祝日のチェックも追加
            } else {
                workday++;
            }
        }
        log.info("days: " + edit.getDays() + " workday: " + workday);
        if (edit.getDays().doubleValue() > workday) {
            throw new Exception(resources.getString("days.over"));
        }
        if (edit.getDays().intValue() != workday) {
            if (StringUtils.isEmpty(edit.getMemo())) {
                throw new Exception(resources.getString("need.memo"));
            }
        }
        // 重複チェック
        for (PaidHoliday holiday : toApply) {
            if ((edit.getStartDate().compareTo(holiday.getStartDate()) >= 0 && edit
                    .getStartDate().compareTo(holiday.getEndDate()) <= 0)
                    || (holiday.getStartDate().compareTo(edit.getStartDate()) >= 0 && holiday
                            .getStartDate().compareTo(edit.getEndDate()) <= 0)) {
                throw new Exception(resources.getString("holiday.overlap"));
            }
        }
        log.info("to check if apply is duplicated: holidays.size() = "
                + holidays.size());
        log.info("to apply: " + edit.toString());
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMdd");
        for (PaidHoliday holiday : holidays) {
            log.info("exists: " + holiday.toString());
            if ((sdf2.format(edit.getStartDate()).compareTo(
                    sdf2.format(holiday.getStartDate())) >= 0 && sdf2.format(
                    edit.getStartDate()).compareTo(
                    sdf2.format(holiday.getEndDate())) <= 0)
                    || (sdf2.format(holiday.getStartDate()).compareTo(
                            sdf2.format(edit.getStartDate())) >= 0 && sdf2
                            .format(holiday.getStartDate()).compareTo(
                                    sdf2.format(edit.getEndDate())) <= 0)) {
                throw new Exception(resources.getString("holiday.overlap"));
            }
        }
        PaidHoliday holiday = new PaidHoliday();
        holiday.setSeq(toApply.size() + 1);
        holiday.setLoginId(edit.getLoginId());
        holiday.setStartDate(edit.getStartDate());
        holiday.setEndDate(edit.getEndDate());
        holiday.setDays(edit.getDays());
        holiday.setMemo(edit.getMemo());
        holiday.setStatus(Common.APPLY_STATUS.EDITING);
        toApply.add(holiday);
        Datastore.put(holiday);
    }

    public void loadApprovers() throws Exception {
        approvers.clear();
        ApproverMeta am = ApproverMeta.get();

        RouteMeta rm = RouteMeta.get();
        Route route = Datastore.query(rm).filter(rm.name.equal(CATEGORY))
                .asSingle();
        approvers.addAll(Datastore.query(am).filter(
                am.routeId.equal(route.getId())).sort(am.seq.asc).asList());
        for (Approver approver : approvers) {
            if (approver.getRole().matches(".*@.*")) {

            } else {
                UserGroup manager = UserGroupPeer.getManager(applyee, approver
                        .getRole());
                if (manager == null) {
                    log.info("役割　" + approver.getRole() + "のマネジャーを見つかりません。");
                    throw new Exception(resources.getString("route.error"));
                }
                log.info(approver.getRole() + " : " + manager.getLoginId());
                if (manager != null) {
                    approver.setLoginId(manager.getLoginId());
                    approver.setUserName(manager.getUserName());
                    approver.setGroupName(manager.getGroupName());
                    approver.setGroupCode(manager.getGroupCode());
                } else {
                    throw new Exception(resources
                            .getString("approver.notfound")
                            + approver.getRole());
                }
            }
        }
    }

    public void loadMaster() {
        PaidHolidayMeta phm = PaidHolidayMeta.get();
        // マスタデータ
        List<PaidHoliday> masters = Datastore.query(phm).filter(
                phm.loginId.equal(applyee),
                phm.status.equal(Common.APPLY_STATUS.MASTER)).asList();
        if (masters.size() == 0) {
            // マスタデータを作成する
            master = new PaidHoliday();
            master.setLoginId(applyee);
            master.setStatus(Common.APPLY_STATUS.MASTER);
            master.setRemainingDays(0.0); // デフォルト０
            Datastore.put(master);
        } else {
            master = masters.get(0);
        }
    }

    public void regEditingHolidays(List<PaidHoliday> holidays) throws Exception {
        for (PaidHoliday holiday : holidays) {
            regEditingHoliday(holiday);
        }
    }

    public void loadEditingHolidays() {
        PaidHolidayMeta phm = PaidHolidayMeta.get();
        // 編集中有給休暇一覧を取得
        List<PaidHoliday> orgApplying = Datastore.query(phm).filter(
                phm.loginId.equal(applyee),
                phm.status.equal(Common.APPLY_STATUS.EDITING)).sort(
                phm.startDate.asc).asList();
        // マスタの残日数は申請中の分を含まないから、その分を減らして、表示する
        for (int i = 0; i < orgApplying.size(); i++) {
            PaidHoliday holiday = orgApplying.get(i);
            log.info("id = " + holiday.getId());
            master.setRemainingDays(master.getRemainingDays()
                    - holiday.getDays());
            toApply.add(holiday);
            holiday.setSeq(i + 1);
        }
    }

    public void processApplies(HttpServletRequest req, String memo) {
        UserService userService = UserServiceFactory.getUserService();
        GlobalTransaction gtx = Datastore.beginGlobalTransaction();
        List<Object> entities = new ArrayList<Object>();
        int i = 0;
        Apply applySummary = new Apply();
        applySummary.setApplyee(applyee);
        applySummary.setCategory(CATEGORY);
        applySummary.setStatus(Common.APPLY_STATUS.APPLYING);
        applySummary.setSeq(i++);
        applySummary.setApprover(null);
        applySummary.setMemo(memo);
        log.info("size of toApply: " + toApply.size());
        for (PaidHoliday holiday : toApply) {
            log.info("holiday.getId() " + holiday.getId());
            applySummary.getRefIds().add(holiday.getId().getId());
        }
        gtx.put(applySummary);
        log.info("summary id = " + applySummary.getId());
        // 　サマリレコードもサマリIDを付けるとよろしくない

        for (PaidHoliday holiday : toApply) {
            holiday.setApplyId(applySummary.getId());
            holiday.setStatus(Common.APPLY_STATUS.APPLYING);
            entities.add(holiday);
        }
        for (Approver approver : approvers) {
            Apply apply = new Apply();
            apply.setApplyee(applyee);
            apply.setCategory(CATEGORY);
            apply.setStatus(Common.APPLY_STATUS.APPLYING);
            apply.setSeq(i++);
            apply.setApprover(approver.getLoginId());
            apply.setSummaryId(applySummary.getId());
            apply.setApproverGroupName(approver.getGroupName());
            apply.setRole(approver.getRole());
            UserMeta um = UserMeta.get();
            User user = Datastore.query(um).filter(um.loginId.equal(applyee))
                    .asSingle();
            apply.setApplyeeName(UserPeer.getName(user));
            apply.setApproverGroupCode(approver.getGroupCode());
            entities.add(apply);
        }
        entities.add(master);
        gtx.put(entities);
        gtx.commit();

        // メール送信
        String domain = req.getServerName();
        String requestUrl = Main.getRequestUrl(req);

        // 第一承認者へ送信
        Mail mail = new Mail(this.getClass().getPackage().getName().replace(
                ".", "/")
                + "/PaidHolidayApply.to.approver.vm");
        // Fromを設定
        mail.setFrom(mailSender.getLoginId(), UserPeer.getName(mailSender));
        mail.addTo(approvers.get(0).getLoginId(), approvers.get(0)
                .getUserName());
        // 件名
        mail.setSubject(resources.getString("apply.mail.subject"));

        // URL設定
        String approveURL = requestUrl + "/approve/ApproveRef";
        approveURL = userService.createLoginURL(approveURL, domain);
        mail.put("url", approveURL);
        mail.send();

        // 自分への送信
        mail = new Mail(this.getClass().getPackage().getName()
                .replace(".", "/")
                + "/PaidHolidayApply.to.applyee.vm");
        // Fromを設定
        mail.setFrom(mailSender.getLoginId(), UserPeer.getName(mailSender));
        mail.addTo(applyee, UserPeer.getName(applyeeUser));
        // 件名
        mail.setSubject(resources.getString("apply.mail.subject"));
        // URL設定
        String applyURL = requestUrl + "/apply/ApplyRef";
        applyURL = userService.createLoginURL(applyURL, domain);
        mail.put("url", applyURL);
        mail.send();

    }

    public PaidHoliday getMaster() {
        return master;
    }

    public void setMaster(PaidHoliday master) {
        this.master = master;
    }

    public List<PaidHoliday> getToApply() {
        return toApply;
    }

    public void setToApply(List<PaidHoliday> toApply) {
        this.toApply = toApply;
    }

    public List<PaidHoliday> getHolidays() {
        return holidays;
    }

    public void setHolidays(List<PaidHoliday> holidays) {
        this.holidays = holidays;
    }

    public List<Approver> getApprovers() {
        return approvers;
    }

    public void setApprovers(List<Approver> approvers) {
        this.approvers = approvers;
    }

    public User getMailSender() {
        return mailSender;
    }

    public void setMailSender(User mailSender) {
        this.mailSender = mailSender;
    }

    /**
     * 整合性チェック
     *
     * @return
     * @throws Exception
     */
    public boolean onApply() throws Exception {
        if (toApply.size() == 0) {
            throw new Exception(resources.getString("apply.empty"));
        }
        if (master.getRemainingDays() < 0) {
            throw new Exception(resources.getString("apply.over"));
        }
        return true;
    }

    // 承認済み、まだ未消化（日付が過ぎてない）有給休暇一覧を取得
    public void loadHolidays(String loginId) {
        List<PaidHoliday> holidayData = PaidHolidayPeer.getHolidays(loginId);

        holidays.addAll(holidayData);
    }

    public void sendErrorMail(List<PaidHoliday> toApply, String message) {
        Mail mail = new Mail(this.getClass().getPackage().getName().replace(
                ".", "/")
                + "/PaidHolidayApply.error.vm");
        // Fromを設定
        mail.setFrom(mailSender.getLoginId(), UserPeer.getName(mailSender));
        mail.addTo(applyee, UserPeer.getName(applyeeUser));
        // 件名
        mail.setSubject(resources.getString("apply.mail.error.subject"));
        //　エラーメッセージ
        mail.put("message", message);
        // 申請内容
        log.info("エラーメール送信、size of toApply: " + toApply.size());
        mail.put("toApply", toApply);
        mail.send();
    }
}
